import base64
import hashlib
import os
import psutil
# import jsonschema
# from tkinter import *
import inspect
import contextlib
import psutil
import threading as my_threading
import time
from json import JSONDecodeError
# ！没有使用的import也【不能删除】, 因为可能是Quicker在用
from jsonpath import jsonpath
from collections import UserString, UserList, UserDict, defaultdict, deque
import itertools
from itertools import chain, groupby, zip_longest, dropwhile
from functools import cmp_to_key, wraps
from operator import gt, eq, lt, ge, le
import copy
import traceback
import requests
from icecream import ic
# from addict import Addict
# from functional import seq
from typing import Iterable, List, Union, Dict, Iterator, \
    Generator, Match, Callable, Any, Tuple
import yaml
import os
import json
import re
import regex
import csv
import urllib.parse as url_parse

from requests import ReadTimeout, ConnectionError


class CsvTest:

    def __init__(self):
        pass

    def csv_test(self, func, case_s: Iterator = None,
                 convert_all=False, is_assert=False,
                 only_num=None, count=-1, case_CsvPath="", hide_args=0,
                 list_e_to_str=False, is_txt=False, del_lastArg=False):
        """
        is_txt的分隔符为: -------
        """
        case_s = list(self.read_yield(case_CsvPath, convert_all=convert_all,
                                      list_e_to_str=list_e_to_str, is_txt=is_txt)) if case_s is None else case_s
        only_num = len(case_s) if only_num and (len(case_s) <= only_num or only_num == -1) else only_num
        count = count if count > 0 else float("inf")
        _count = 0
        for num, line in enumerate(case_s, 1):
            line = line[:-1] if del_lastArg else line
            if only_num and only_num != num:
                continue
            _count += 1
            if _count > count:
                break
            if is_assert:
                *args, expect = line
                res = func(*args)
                assert res == expect, (f"\n[参数...]   [期望结果, 实际结果]"
                                       f"\n{args}   {[expect, res]}")
            else:
                # 判断参数类型选择执行
                if isinstance(line, tuple):
                    res = func(*line)  # line: list. 有多个参数就传多少个
                else:
                    res = func(line)
                # 判断返回值类型
                if isinstance(func, type):
                    res = res()
                elif isinstance(res, Generator):
                    res = list(res)
                if hide_args:
                    print(f"{res}\n")
                else:
                    print(f"{res}   {line}\n")

    def _convert(self, e, d: dict, is_force=False, list_e_to_str=False):
        if is_force:
            return eval(e) if not list_e_to_str else re.split(r" *, *", e.strip("[]"))
        for postfix, func in d.items():
            if e.endswith(postfix):
                # 使用to_str的原因: eval不支持ustr
                e = ustr(e).del_postfix(postfix).to_str()
                return func(e)
        return e

    # _case_csv的参数顺序不能变, 因为和csv_test绑定了
    def read_yield(self, path="", quotechar="⏎", convert_all=False,
                   list_e_to_str=False, is_txt=False):
        # 类型转换: 根据结尾判断, 否则默认为字符串
        d = {
            ".list": lambda e: eval(e),  # 列表
            ".dict": lambda e: eval(e),  # 数字
            ".int": lambda e: int(e),  # 字典
        }
        path = path or r"F:\A_RuanJian" \
                       r"\Py_project\pro_test\case_s.csv"
        # 类型转换: "[]"的是列表, "{}"是字典, "."结尾的是数字, 其他是字符串
        with open(path, encoding="utf-8") as fi:
            if is_txt:
                yield from ((e.rstrip(),) for e in
                            fi.read().split("-------"))
            else:
                fi = csv.reader(fi, **{'delimiter': "\t",
                                       'quotechar': quotechar})  # 斜杠作为包围符)
                # 符合的, 则去除相应后缀+执行函数. 否则.不转换
                yield from (
                    tuple(self._convert(e, d, convert_all, list_e_to_str) for e in line)
                    for line in fi if line and not line[0].startswith("#")  # 筛选: 去除空行、#开头的
                )


if "quicker":
    class qk_decorate:
        """
        Quicker与python的一些问题总结/记录
        1. del \r
        2. "" to None. 默认值是None时, 需要把空字符串转None
        3. strip first. 有时候传入的值需要先strip
        4. 服务端的返回值必须是json支持的对象, 比如不能是ustr也不能是func
        """

        def __init__(self, to_strip: int = 0, del_empty: int = 1):
            self.del_empty = del_empty
            self.to_strip = to_strip

        def del_r(self, *args):
            return json.loads(json.dumps(args).replace("\\r", ""))

        def args_func(self, *args):
            # 去掉\r. 并对所有字符串参数修剪
            args = json.loads(json.dumps(args).replace("\\r", ""))
            return (
                e.strip() if isinstance(e, str) else e
                for e in args
            ) if self.to_strip else args

        def return_func(self, obj):
            # 如果是字典则去掉其中的空值.
            return {k: v for k, v in obj.items() if v} \
                if self.del_empty and isinstance(obj, dict) else obj


    def quicker(args_to_strip=0, return_del_empty=1):
        def d2(func):
            qk = qk_decorate(to_strip=args_to_strip, del_empty=return_del_empty)

            # 外部传入的参数都是文本, 而不是None.

            # 涉及到None的参数都必须如此处理
            @wraps(func)
            def d3(*args, **kwargs):
                args = qk.args_func(*args)
                return_ = qk.return_func(func(*args, *kwargs))
                return return_

            return d3

        return d2

if "各种类":
    class Proc(psutil.Process):
        def __init__(self, pid):
            super().__init__(pid)
            self.should_kill = False

        def get_port(self):
            is_connect = lambda p: p.connections()
            get_port = lambda p: p.connections()[0].laddr.port
            return get_port(self) if is_connect(self) else 0

        def name_neat(self):
            return self.name().rstrip(".exe")

        def __str__(self):
            return f"{Proc.__name__}" \
                f"(name={self.name_neat()}, port={self.get_port()}, pid={self.pid})"
            # f"pid={self.pid})"


    class _Print:
        count_record = 0
        num = 0

        def __init__(self, pre_num=0, index_=0, name_=1, type_=0, exit_=0,
                     count_exit=None, count_not_exit=None,
                     not_iter_=0, not_repr_=1, repr_=0, qk_msg=0):
            # 使用None的原因: 这些参数预期不是布尔值,而是数字.
            self.pre_num = pre_num
            self.count_exit = count_exit
            self.count_not_exit = count_not_exit
            self.index_ = index_
            self.name_ = name_
            self.type_ = type_
            self.exit_ = exit_
            self.repr_ = repr_
            self.not_iter_ = not_iter_
            self.not_repr_ = not_repr_
            self.is_iterable = 0
            self.qk_msg = qk_msg
            self.all_msg = []

        def __call__(self, *args):
            if "回溯+提取参数名":
                # 获取执行的那一行
                s = inspect.getframeinfo(inspect.currentframe().f_back).code_context[0]
                # 正则提取括号内的的所有参数, 并分隔. 由于是有序的,不用担心值相同的问题
                m = re.search(r"pp\(.*?\)\((.*)\)", s).group(1)
                name_s = re.split(r", ?", m)
            if "记录运行次数. 用于循环":
                count_exit, count_not_exit, exit_ = self.count_exit, self.count_not_exit, self.exit_
                _Print.count_record += 1  # 必须是类变量而不是实例变量
                if (count_exit is not None) and self.count_record > count_exit:
                    exit()
                # noinspection PyChainedComparisons
                if (count_not_exit is not None) and self.count_record > count_not_exit:
                    return None
            if "记录运行次数":
                if self.pre_num:
                    _Print.num += 1
            self.main(*args, name_s=name_s)

        def _print(self, s=""):
            s = uli(self.all_msg).join("\n") if s == "" else s
            if self.qk_msg:
                self.qk_msg_f(s)
            else:
                print(s)

        def qk_msg_f(self, msg, msg_type=0):
            # 发送Quicker提示
            qk_id = "34d02a64-7a20-414d-9db9-f6bf54ec83c2"
            self.cc_request(dict(qk_id=qk_id, msg=msg))
            # msg = urlEncode(msg)
            # s = f"quicker:runaction:{qk_id}?{msg}"
            # os.popen(f"start {s}").read()

        def cc_request(self, arg1: dict, close=0):
            url = "http://localhost:5006"
            url = url+"/off" if close else url
            arg1 = "?"+"&".join(
                f"{k}={urlEncode(v.encode('gbk'))}"
                for k, v in arg1.items()
            )
            url += arg1
            # resp = requests.get(url, arg1, timeout=1)
            # arg1 = "?key="
            # arg1 += urlEncode("哈哈".encode("gbk"))
            try:
                resp = requests.get(url, None, timeout=0.1)
                text = resp.content.decode("gbk")
            except (ConnectionError, ReadTimeout):
                pass

        def is_iterable_not_str(self, obj):
            return isinstance(obj, Iterable) and not isinstance(obj, (str, UserString))

        def should_repr(self, value):
            return (
                    not self.not_repr_ and
                    (self.repr_ or
                     str(value) == "" or
                     any((isinstance(value, str) and char in value)
                         for char in ["\r", "\n", "\t"])))

        def join_print(self, value, index=0, name="", sep=' | ', append=0):
            join_li = [str(_Print.num) if self.pre_num else IgnoreObj(),
                       ustr(index).put_pair("{}") if self.index_ and index else IgnoreObj(),
                       ustr(name).put_pair("{}") if self.name_ and name else IgnoreObj(),
                       # type(value) if self.type_ else re.sub(r"<class '(.*?)'>", r"<\1>", str(type(value))),
                       type(value) if self.type_ else IgnoreObj(),
                       "[repr]"+repr(value) if self.should_repr(value) else value
                       ]
            # IgnoreObj代表忽略, 用于与None进行区分
            each = sep.join(str(e)
                            for e in join_li
                            if not isinstance(e, IgnoreObj))
            if append:
                self.all_msg.append(each)
            else:
                self._print(each)

        def _dict(self, d):
            first_key, first_value = next(iter(d.items()))
            # 如果字典的值是可迭代对象(非str), 则使用自定义方法.
            # 否则使用json.
            # 如果key是数字则不使用json, 因为json的数字会变成字符串
            self._print(ustr().dict_format(d))
            # if self.is_iterable_not_str(first_value) or \
            #         isinstance(first_key, int):
            #     return self._print(ustr().dict_format(d))
            # else:
            #     return self._print(json_dumps(d))

        def main(self, *args, name_s=()):

            if len(args) == 1:
                first = args[0]
                if re.search(r"^<class '.*?'>$", repr(first)):
                    self.join_print(first, append=1)
                elif not first:
                    self.join_print(first, append=1)
                elif self.repr_:  # 注意repr只有对字符串对象才有用
                    self.join_print(repr(first), append=1)
                elif isinstance(first, dict) and first:
                    self._dict(first)
                elif self.is_iterable_not_str(first) and not self.not_iter_:
                    self.is_iterable = 1
                    for i, value in enumerate(first):
                        self.join_print(value, i, append=1)
                else:
                    self.join_print(first)
            elif len(args) == 0:
                self.join_print("")
            else:  # 多个参数时,打印变量名
                for i, (name, value) in enumerate(zip(name_s, args), 1):
                    self.join_print(value, i, name, append=1)
            self._print()
            if self.exit_: exit()


    def deco(func):
        # 如果第一个参数不是ustr实例, 就转换

        @wraps(func)
        def run(*args, **kwargs):
            s = args[0]
            s = ustr(s) if not isinstance(s, ustr) else s
            args = (s,)+args[1:]
            return func(*args, **kwargs)

        return run


    def deco_all(deco):
        def run(cls):
            for k, func in cls.__dict__.items():
                if not re.match(r'__.+?__', k) and callable(func):
                    setattr(cls, k, deco(func))
            return cls

        return run


    class ReplRestore:
        """替换与恢复"""

        def __init__(self, name=""):
            self.li = []
            self.left, self.right = "<<<", ">>>"
            self.prefix = name

        def repl(self, pat, s: str):
            """
            :param pat: 必须是原始字符串且使用分组,因为要取组一
            :param s:
            """
            li_code = self.li
            left, right = self.left, self.right
            count = -1

            def repl(m: Match):
                nonlocal count
                count += 1
                try:
                    g1 = m.group(1)
                except (IndexError,):
                    raise Exception("请使用分组, 为了统一提取必须使用分组取值")
                li_code.append(ustr(g1).indent_back())
                return f"{left}{self.prefix}{count}{right}"

            s = re.sub(pat, repl, s)
            return s

        def restore(self, s, li=()):
            li = self.li if not li else li
            left, right = self.left, self.right
            s = re.sub(rf"{left}{self.prefix}(\d+){right}",
                       lambda m: li[int(m.group(1))], s)
            return s


    class uli(UserList):
        """
        1. join
        2. try index, except li[-1]
          有时候会出现无法循环的情况 原因不明
        """

        def __init__(self, li=None):
            self.data = None
            if li is None:
                li = []
            super().__init__(li)
            self._li = self.data

        @classmethod
        def del_near_repeat(cls, li: List):
            """
            删除相邻重复元素
            """
            j = 1
            for i in range(j, len(li)):
                if not (li[i] == li[i-1]):
                    li[j] = li[i]
                    j += 1
            for i in range(j, len(li)):
                li.pop()

        def join(self, sep=""):
            return sep.join(self._li)

        def len(self):
            return len(self._li)

        def distinct(self, new=1):
            # 不能直接使用self, 会导致重复递归(原因不明)
            return sorted(set(self._li), key=self._li.index)

        def first(self, default=None):
            # 如果没有, 则返回默认值
            try:
                return self._li[0]
            except (Exception,):
                return default

        def last(self, default=None):
            try:
                return self._li[-1]
            except (Exception,):
                return default

        def get(self, index, default=None):
            try:
                return self._li[index]
            except (Exception,):
                return default

        def __getitem__(self, item):
            # 如果超长索引, 则获取最后一个
            try:
                return self._li[item]
            except (Exception,):
                return self._li[-1]


    class ustr(UserString):
        """
        1.user regex
        """

        def __init__(self, s: Union[str, int, UserString] = ""):
            self.data = None
            super().__init__(s)
            self.ss: str = self.data.replace("\r", "")

        def indent_add(self):
            """每行开头增加缩进
            :return:
            """
            return re.sub(r"(?m)^", r"    ", self.ss)

        def sub_take(self, pat) -> Tuple[str, list]:
            """
            :param pat 必须使用分组, 仅支持一个分组
            :param s
            """

            def repl(m: Match):
                groups.append("".join(m.groups()))
                return ""

            groups = []
            s = regex.sub(pat, repl, self.ss)
            return s, groups

        def sub_take_map(self, dict_: dict, prefix="", postfix="") -> Tuple[str, list]:
            """根据字典, 把符合keys的字符串提取并删除, 再映射成value
            pat: (?m)^ prefix join_keys postfix
            :return s, value
            """
            pat = self.join_to_pat(dict_.keys())
            pat = prefix+pat+postfix
            _ = self.sub_take(pat)
            return _

        def join_to_pat(self, li: Iterable):
            pat = "|".join(re.escape(e) for e in li)
            return f"({pat})"

        def md5(self, salt='') -> str:  # 加盐，盐就是额外追加的字符串
            s = self.ss+salt
            return hashlib.md5(s.encode()).hexdigest()

        def base64_encode(self) -> bytes:
            return base64.b64encode(self.ss.encode())

        def base64_decode(self, byte: bytes):
            return base64.b64decode(byte).decode()

        def toUnicode(self) -> str:
            return self.ss.encode('unicode_escape').decode('utf-8')

        def toStrFromUnicode(self) -> str:
            return self.ss.encode("utf-8").decode("unicode_escape")

        def urlEncode(self):
            return urlEncode(self.ss)

        def urlDecode(self):
            return urlDecode(self.ss)

        def line(self, n=0, all_=0):
            """获取自动行, 没有则返回字符串
            删除前后的换行符,拆分
            不能使用splitlines, 因为要么会删除每行的前后空白符,要么会保留换行符
            """
            self.splitlines()
            li = self.strip("\n").split("\n")
            if all_:
                return li
            return uli(li).get(n, "")

        def indent_back(self):
            return "\n".join(self._indent_back())

        def _indent_back(self):
            """
            去除第一行的缩进, 并记录次数
            后续每行都去除同样次数
            """
            li = iter(self.line(all_=1))
            s, n = re.subn(r" {4}|\t", r"", next(li))
            yield s
            for e in li:
                n = -1 if n == 0 else n
                s = re.sub(r" {4}|\t", r"", e, count=n)
                yield s

        def indent_del(self, all_=True):
            return self.del_indent()

        def del_indent(self):
            """
            删除每行所有缩进
            正则: 删除每行开头的空格或制表符
            """
            s = self.ss
            return re.sub(r"(?m)^[\t ]*", r"", s)

        def del_prefix(self, prefix):
            prefix = re.escape(prefix)
            return self.sub(rf"^{prefix}", r"", 1)

        def del_postfix(self, postfix):
            postfix = re.escape(postfix)
            return self.sub(rf"{postfix}$", r"", 1)

        def to_str(self):
            # 使用to_str的原因: 有些情况不支持ustr,比如eval()
            return str(self)

        def dict_format(self, d):
            beg, end = "{}"
            k_ = lambda k: ustr(k).put_pair("''") if isinstance(k, str) else k
            _ = "\n".join(f"{k_(k)}: {v}"
                          for k, v in d.items())
            _ = "\n".join([beg, _, end])
            return _

        def lineCount(self):
            return self.strip().count("\n")+1

        def stripLines(self, chars=None, left=None, right=None):
            # 外部传入的参数都是文本, 而不是None.
            # 涉及到None的参数都必须如此处理
            chars = None if chars == "" else chars
            li = (lambda s: s.strip(chars),
                  lambda s: s.lstrip(chars),
                  lambda s: s.rstrip(chars))
            func = li[2] if right else li[1] if left else li[0]
            if self.lineCount() > 1:
                res = "\n".join(
                    func(line)
                    for line in self.strip("\n").splitlines())
            else:
                res = func(self)
            return res

        def lstripLines(self, chars=None):
            return self.stripLines(chars, left=1)

        def rstripLines(self, chars=None):
            return self.stripLines(chars, right=1)

        def replaceMoreToMore(self, old_s: Iterable, new_s: Iterable):
            """一一对应的替换, 对称替换
            1,2,3 -> a,b,c
            备注: 不支持正则
            """
            pat = "|".join(re.escape(e) for e in old_s)
            d = dict(zip(old_s, new_s))
            repl = lambda m: d.get(m.group(), "")
            return re.sub(rf"{pat}", repl, self.ss)

        def rrepr(self):
            """反转义(反序列化), 对\r,\n,\t
            "1.映射"
            "2.序列化. 增加双引号"
            "3.eval作为表达式运行"
            """

            def repl(m):
                v = m.group()
                # v = dict(zip([r"\r", r"\n", r"\t"], ["\r", "\n", "\t"]))[v]
                # v = json.loads(ustr(v).surround("\"\""))
                v = eval(ustr(v).surround("\"\""))
                return v

            return re.sub(r"\\r|\\n|\\t", repl, self.ss)

        def takeLeft(self, flag=" = "):
            # 先拿走等于号左边的字符串, 后续再添加
            def repl(m: Match):
                nonlocal left
                left = m.group()
                return ""

            left = ""
            _ = re.sub(rf".*?{re.escape(flag)}", repl, self.ss)
            return left, _

        def takeRight(self, flag="\n", s=None):

            def repl(m: Match):
                nonlocal right
                right = m.group()
                return ""

            right = ""
            s = self.ss if s is None else s
            _ = re.sub(rf"{re.escape(flag)}$", repl, s)
            return _, right

        def take_LeftRight(self):
            left, s = self.takeLeft()
            s, right = self.takeRight(s=s)
            return left, s, right

        def surround(self, pair):
            mid = len(pair)//2
            return f"{pair[:mid]}{self.ss}{pair[mid:]}"

        def put_pair(self, *pair_s):
            for p in pair_s:
                self.ss = self.surround(p)
            return self.ss

        def __sub__(self, other):
            return self.replace(other, "")

        def __rsub__(self, other: str):
            return other.replace(self.ss, "")
            # return self.replace(other, "")

        def del_first(self, old):
            return ustr(self.ss.replace(old, "", 1))

        def rdel_first(self, old):
            return self.rreplace(old, "", 1)

        def del_firstMore(self, old_s):
            return self.replaceMore(old_s, "", 1)

        def rdel_firstMore(self, old_s):
            return self.rreplaceMore(old_s, "", 1)

        def contains(self, sub_str):
            return sub_str in self.ss

        def containsAny(self, *sub_str_s):
            return any(sub_str in self.ss for sub_str in sub_str_s)

        def containsAll(self, sub_str_s: Iterable[str]):
            return all(sub_str in self.ss for sub_str in sub_str_s)

        def replaceMore(self, old_s: Iterable, new, count=0):
            pat = "|".join(re.escape(e) for e in old_s)
            # new = re.escape(new) # new不需要转义
            return ustr(re.sub(pat, new, self.ss, count))

        def rreplaceMore(self, old_s: Iterable, new, count=0):
            pat = "|".join(re.escape(e) for e in old_s)
            # new = re.escape(new) # new不需要转义
            return regex.sub(pat, new, self.ss, count, regex.R)

        def rreplace(self, old, new: str, count=-1):
            # 利用split函数删除旧字符串, 再用join替换为新字符串
            # pp(type_=1)(new)
            return ustr(new.join(self.rsplit(old, count)))

        def search(self, pattern, flags=0):
            return re.search(pattern=pattern, string=self.ss, flags=flags)

        def sub(self, pattern, repl, count=0, flags=0):
            return ustr(re.sub(pattern=pattern, repl=repl,
                               string=self.ss, count=count, flags=flags))

        def split_re(self, pattern, maxsplit=0, flags=0):
            return re.split(pattern=pattern, string=self.ss, maxsplit=maxsplit, flags=flags)

        def findall(self, pattern, flags=0):
            return re.findall(pattern=pattern, string=self.ss, flags=flags)

        def finditer(self, pattern, flags=0):
            return re.finditer(pattern=pattern, string=self.ss, flags=flags)


    class IgnoreObj:
        pass


    class udi(UserDict):
        """
        1.all value must be true(目的是为了条件判断时,方便用or and)
        2.if_func: then (用于条件判断)
            if first key si func, then all key same
        """

        def __init__(self, d):
            super().__init__(d)
            self.d = self.data
            assert all(self.d.values()), "all value must be true"
            self.key_is_func = False
            if callable(next(iter(self.d.keys()))):
                assert all(callable(k) for k in self.d.keys()), "if first key si func, then all key same"
                self.key_is_func = True

        def get(self, item, default=None):
            if self.key_is_func:
                for k, v in self.d.items():
                    # noinspection PyCallingNonCallable
                    if k(item): return v
                return default
            else:
                self.d.get(item, default)


    class ListSortByCount:
        """用户选择/列表按频率排序"""

        def __init__(self):
            self.li = ["aa,1", "bb,2", "cc,3"]
            self.d = {}

        def func1(self):
            """
            用户选择:: 每次用户会选择一个元素, 结果会返回元素的右半部分 -> right
            计数::  (映射关系)  right:count  每次对应的数量加1
            排序:: 先提取原始的右边部分, 再映射, get_right() : 数量,
            负数:: 要根据数量降序排, 所以是负数.
            备注:
                不能通过列表索引来映射, 因为列表索引被移动了就会变
                不能让字典预先生成, 因为后续list可能会被用户删减
            """
            li, d = self.li, self.d
            choice = input("\n".join(li))
            d[choice] = d.get(choice, 0)+1  #
            get_right = lambda e: e.split(",")[1]
            li.sort(key=lambda e: -d.get(get_right(e), 0))
            # pp()(li, d)
            return choice

        def __call__(self, *args, **kwargs):
            while True:
                choice = self.func1()
                if choice == "0":
                    break


    class MyError(Exception):
        def __init__(self, value):
            self.value = value

        def __str__(self):
            return '\n'+self.value


    class Err:
        json_decode = JSONDecodeError, "json反序列化异常"
        index_err = IndexError, '索引异常：\n"{}" not in "{}"'
        assert_err = AssertionError, '【断言错误】\n{}'
        assert_err_args = AssertionError, '{}'
        args_err_msg = "参数错误：用例函数'{}'的参数必须带默认值"


    class MyThread(my_threading.Thread):
        def __init__(self, target, group=None, name=None,
                     args=(), kwargs=None, *, daemon=None):
            super().__init__(group=group, name=name, args=args, kwargs=kwargs, daemon=daemon)
            self.target = target
            self.args = args

        def run(self):
            # 不能放在构造函数里, 会变慢
            self.__res = self.target(*self.args)

        def get_res(self):
            # 获取线程返回值
            try:
                return self.__res  # 如果子线程不使用join方法，此处可能会报没有self.result的错误
            except AttributeError:
                raise AttributeError('线程返回值获取错误！不能没有join')


    class OpYaml:
        yaml.warnings({'YAMLLoadWarning': False})
        dir01 = os.path.dirname(__file__)
        f_name = r'\_history.yaml'
        f_path = dir01+f_name
        key = 'adb_connect'

        def __init__(self, path=None):
            self.path = path or self.f_path
            self.__data = self.__read()
            self.data_list = self.__data[self.key]

        def __read(self):
            with open(self.path, 'r', encoding='utf-8') as fi:
                data = yaml.load(fi)
            return data

        def write(self):
            data = self.__data
            with open(self.path, 'w', encoding='utf-8') as fi:
                yaml.dump(data, fi, allow_unicode=True)

    # def thread_join(params=None, threads=None, func_main=None):
    #     # params = [(api01, 1), (api02, 2)]
    #     t01 = None
    #     if params:
    #         t01 = [MyThread(target=i[0], args=(i[1:]), daemon=True)
    #                for i in params]
    #     elif threads:
    #         t01 = threads
    #     [i.start() for i in t01]
    #     [i.join() for i in t01]
    #     res_list = [i.get_res() for i in t01]
    #     if func_main:
    #         return func_main(*res_list)
    #     else:
    #         return res_list
    #
    # class MyGUI:
    #     def __init__(self):
    #         self.window = Tk()
    #
    #         # 设置主窗口属性(窗口名, 大小, 位置, 背景色等)
    #         self.window.title('JSON处理工具')
    #         # window.geometry('800x600+50+50')
    #         self.window['bg'] = 'GhostWhite'
    #         self.window.attributes('-alpha', 1)
    #
    #         # 添加两个文本框
    #         self.input_text = Text(self.window)
    #         self.input_text['bg'] = 'pink'
    #         self.input_text.grid(row=0, column=0, sticky=W)
    #
    #         self.result_labe = Text(self.window)
    #         self.result_labe['bg'] = 'blue'
    #         self.result_labe.grid(row=0, column=2)
    #
    #         self.to_json_button = Button(
    #             self.window, text='to_json', bg='lightblue', width=10, command=self.to_json)
    #         self.to_json_button.grid(row=1, column=1)
    #
    #         self.window.mainloop()
    #
    #     # def set_window(self, window):
    #     def fill_required(self, schema: dict) -> dict:
    #         pro = 'properties'
    #         req = 'required'
    #         items = 'items'
    #         d01 = schema[pro]
    #         # 递归
    #         if d01:
    #             key_s = [k for k in d01.keys()]
    #             schema[req] = key_s
    #             # 第二层
    #             for k01, v in d01.items():
    #                 if isinstance(v, dict) and d01[k01].get(pro):
    #                     d02 = schema[pro][k01][pro]
    #                     if d02:
    #                         key_s = [k for k in d02.keys()]
    #                         schema[pro][k01][req] = key_s
    #                         # 第三层
    #                         for k02, v in d02.items():
    #                             if isinstance(v, dict):
    #                                 if d02[k02].get(pro) or d02[k02].get(items):
    #                                     try:
    #                                         d03 = schema[pro][k01][pro][k02][pro]
    #                                     except:
    #                                         d03 = schema[pro][k01][pro][k02][items][pro]
    #                                     # print(f'd03: {d03}')
    #                                     if d03:
    #                                         key_s = [k for k in d03.keys()]
    #                                         schema[pro][k01][pro][k02][items][req] = key_s
    #                                         # 第4层
    #                                         # for k03, v in d03.items():
    #                                         #     if isinstance(v, dict) and d01[k01][pro][k02][pro][k03].get(pro):
    #                                         #         d04 = schema[pro][k01][pro][k02][pro][k03][pro]
    #                                         #         print(d04)
    #                                         #         if d04:
    #                                         #             key_s = [k for k in d04.keys()]
    #                                         #             schema[pro][k01][pro][k02][pro][k03][req] = key_s
    #         return schema
    #
    #     def to_json(self):
    #         # 置空text
    #         self.result_labe.delete(0.0, END)
    #
    #         def to_jsonschema(json_data, result):
    #             '''
    #                 递归生成jsonschema
    #             :return:
    #             '''
    #             if isinstance(json_data, dict):
    #                 is_null = True
    #                 result.append('{')
    #                 result.append("'type': 'object',")
    #                 result.append("'properties': {")
    #                 for k, v in json_data.items():
    #                     is_null = False
    #                     result.append("'%s':" % k)
    #                     to_jsonschema(v, result)
    #                     result.append(',')
    #                 if not is_null:
    #                     result.pop()
    #                 result.append('}')
    #                 result.append('}')
    #             elif isinstance(json_data, list):
    #                 result.append('{')
    #                 result.append("'type': 'array',")
    #                 result.append("'items': ")
    #                 to_jsonschema(json_data[0], result)
    #                 result.append('}')
    #             elif isinstance(json_data, int):
    #                 result.append("{")
    #                 result.append("'type': 'number'")
    #                 result.append('}')
    #             elif isinstance(json_data, str):
    #                 result.append("{")
    #                 result.append("'type': 'string'")
    #                 result.append('}')
    #
    #             return "".join(result)
    #
    #         json_data = self.input_text.get(0.0, END).strip().replace("\n", "")
    #         result = []
    #         try:
    #             test_data = to_jsonschema(eval(json_data), result)
    #             params = eval(test_data)  # 获取初步生成的schema
    #         except (Exception,):
    #             self.result_labe.insert(1.0, '输入的JSON数据有问题, 请检查')
    #         else:
    #             try:
    #                 params = self.fill_required(params)  # 给required加入所有字段
    #             except Exception:
    #                 self.result_labe.insert(1.0, '填入required列表时，发生错误')
    #             else:
    #                 self.result_labe.insert(1.0, json.dumps(params, indent=4))

if "各种函数":
    # def __match(*args):
    #     raise Exception("禁用该函数, 因为它必须从开头匹配, 容易与其他语言混淆")
    #
    #
    # re.match = __match

    def read_file(path, encoding="utf8"):
        with open(path, encoding=encoding) as fi:
            text1 = fi.read()
        return text1


    def write_file(path, text, encoding="utf8"):
        with open(path, "w", encoding="utf8") as fi:
            fi.write(text)


    def one_of(a, b, if_: Union[bool, int]):
        return a if if_ else b


    def map_list(func, li):
        return list(map(func, li))


    def each(eval_: str, li, type_return: type = None,
             attr: str = None):
        """
        :param li:
        :param eval_:
        :param type_return: list则返回list, 否则默认返回generate
        :param attr:
        :return:
        """
        # ele name: e
        # 1.直接eval
        if eval_:
            gen = (eval(eval_) for e in li)
            if type_return is not None:
                return type_return(gen)
            else:
                return gen
        else:
            # 2.getattr
            right = ""
            if attr.endswith("()"):
                attr, right = attr.replace("()", ""), "()"
            li2 = []
            for e in li:
                e = getattr(e, attr)
                if right:
                    e = e()
                li2.append(e)
        return li2


    def each_i(range_, li):
        for i in range(len(li))[::-1]:
            yield li[i]


    def yaml_load(data: str):
        return yaml.load(data)


    def yaml_dump(data: Union[dict, list]):
        return yaml.dump(data, allow_unicode=True)


    def proc_search(port=0, name="", killall=0, fuzzy=False, ignore_current=0, kill_self=0):
        """
        fuzzy: 模糊查找
        根据进程名、端口查找进程.
        参数为假时, 忽略
        忽略当前进程
        """
        is_connect = lambda p: p.connections()
        get_port = lambda p: p.connections()[0].laddr.port
        cmp_port = lambda p: (is_connect(p) and get_port(p) == port) if port else True
        cmp_port_fuzzy = lambda p: (is_connect(p) and str(port) in str(get_port(p))) if port else True
        cmp_name = lambda name: (name in p.name()) if name else True
        cmp_current = lambda p: (os.getpid() != p.pid) if ignore_current else True

        if fuzzy:
            cmp_port = cmp_port_fuzzy
        li = []
        for p in psutil.process_iter():
            p = Proc(p.pid)
            if p.name_neat() == "System Idle Process":
                continue  # 忽略Idle, 也就是PyCharm
            if kill_self and p.pid == os.getpid():
                p.should_kill = 1
            # 检查是否存在占用端口的其他进程
            if p.get_port() in [5001, 5002] and p.name_neat() != "python":
                raise Exception(f"存在占用端口\"{p.get_port()}\"的其他进程: \"{p.name_neat()}\"")
            if (port == "any" or cmp_port(p)) and cmp_current(p):
                p.should_kill = 1 if killall else 0
                # pp()(p)
                li.append(p)

        def kill_(pid):
            p = Proc(pid) if isinstance(pid, int) else pid
            # pp(qk_msg=1)(f"端口为{p.get_port()}的http服务已关闭.")
            p.kill()

        def kill_main(li):
            # 把主端口的移动到最后才kill
            main_port = 5001
            li = [
                p
                for p in li
                if p.should_kill and p.get_port() != 0
            ]
            # print([p.get_port() for p in li], 1)
            index = next((
                i
                for i, p in enumerate(li)
                if p.get_port() == main_port
            ), None)
            # print(index)
            if index is not None:
                li[index], li[-1] = li[-1], li[index]
            # kill_all
            # print([p.get_port() for p in li], 2)
            # exit()
            list(map(kill_, li))

        if killall or kill_self:
            kill_main(li)

        return li


    pp = _Print

    urlEncode = lambda s: (url_parse.quote(s))
    urlDecode = lambda s: (url_parse.unquote(s))
    csv_test = CsvTest().csv_test


    def group_by(li: Iterable,
                 f_add: Callable[[Any, Any], bool] = None,
                 f_keep_last: Callable[[Any], bool] = None,
                 e_attr: str = ""):
        """
        分组_相邻元素
        第一个元素直接添加到分组. 从第二个开始判断
        """
        li = iter(li)
        a = next(li)
        f_add = (lambda a, b: a == b) if (f_add is None) else f_add
        f_keep_last = (lambda b: False) if (f_keep_last is None) else f_keep_last
        get_e = (lambda e: getattr(e, e_attr)) if e_attr else (lambda e: e)

        tmp = [get_e(a)]
        for b in li:
            if not f_add(a, b):
                yield tmp
                tmp = []

            a = b if not f_keep_last(b) else a
            tmp.append(get_e(b))
        yield tmp


    def group_by_2(li, f_key=None, f_value=None):
        """分组_不相邻元素"""
        d = {}
        f_key = f_key or (lambda e: e)
        f_value = f_value or (lambda e: e)
        for e in li:
            d.setdefault(f_key(e), []).append(f_value(e))  # 添加到一个列表
            # d.setdefault(key, []).extend(value) # 合并多个列表
        return d


    def if_else(if_s: Dict[str, callable], e, mode="",
                else_=None, del_postfix="未实现"):
        """ 条件分支判断 封装优化
        mode = starts  or  ends  or  in  or  regex
        """
        # 另一种思路: 先提取, 再dict映射
        #   但提取时要用正则, 也就是需要先转义.所以性能不好. 或者应该先生成字典和正则, 再进行提取和匹配
        #   但不灵活, 如果key自身就是正则就需要另外处理
        if_: callable = {'ends': lambda key: e.endswith(key),
                         'starts': lambda key: e.startswith(key),
                         'in': lambda key: key in e,
                         'regex': lambda key: regex.search(key, e)}[mode]
        else_value = else_ if else_ else e
        return next((
            func(e)
            for key, func in if_s.items() if if_(key)
        ), else_value)


    def case(if_s: Dict[str, callable], e, mode="", else_=None):
        return if_else(if_s, e, mode, else_)


    def replaceSym(s, pattern_s):
        """对称替换
        split,join,dict,
        sub, d.get, m.groups
        :param s:
        :param pattern_s:
            a 1
            b 2
        """
        # noinspection PyTypeChecker
        d = dict((
            line.split()
            for line in pattern_s.strip().splitlines()
        ))
        pat = "|".join((
            re.escape(f"{k}")
            for k in d.keys()
        ))
        repl = lambda m: d.get(m.group(), "")
        s = re.sub(pat, repl, s)
        return s


    json_dumps = lambda d: json.dumps(d, ensure_ascii=False, indent=2)


    def str_to_json(keys: str, values: str, is_splitLines=True, sep=None, dumps=True):
        """两个字符串 to dict
        """
        # 分隔
        keys, values = keys.strip(), values.strip()
        if is_splitLines:
            keys = keys.splitlines()
            values = values.splitlines()
        else:
            keys.split(sep)
            values.split(sep)
        # 转字典(dict)
        d = dict(zip(keys, values))
        # 格式化(str)
        if dumps:
            print(json_dumps(d))
            return json_dumps(d)
        else:
            print(d)
            return d


    def swap(i, j, li):
        """交换元素, 根据索引
        :param i index1
        :param j index2
        :param li list
        """
        li[j], li[i] = li[i], li[j]


    def to_repr(s):
        """
        :param s 非原始字符串
        转义不可见字符 (re.esc不会真的转义)
        原理: 转义时忽略反斜杠
        """
        # 确定结果的边界引号是单还是双, 与参数反转
        _ = '"' if ("'" in s) else "'"
        _ = (
            repr(e).strip(_)
            for e in s
            if s != "\\"
        )
        return "".join(_)


    def toStr(*args):
        return "".join(map(str, args))


    class _Context:
        x = None


    @contextlib.contextmanager
    def context(func1: Callable, func2: Callable, arg1):
        res = func1(arg1)
        _Context.x = res
        yield _Context
        _Context.x = func2(_Context.x)


    @contextlib.contextmanager
    def try_ex(obj_msg: tuple, *args_msg):
        """
        :param obj_msg: Err对象的属性, 改属性包含异常对象、msg
        :param args_msg: 额外添加的参数, 用于拼接msg
        """
        err_obj, msg = obj_msg
        try:
            yield
        except err_obj as err:
            if args_msg:  # 如果存在额外的参数, 则拼接.否则返回空元组
                args_msg = (
                    # 如果是可迭代对象,并且不是字符串。就用join来拼接
                    ", ".join(item)
                    if isinstance(item, Iterable) and not isinstance(item, str)
                    else item
                    for item in args_msg
                )
                msg = msg.format(*args_msg)
            raise MyError(msg) from err


    def assert_(if_, msg, *msg_args):
        """
        :param if_: 判断条件（正确的）
        :param msg: msg
        :param msg_args: msg的参数
        """
        # 如果是字符串并且不为空, 则视为表达式, 使用eval运行表达式
        if if_ and isinstance(if_, str):
            if_ = eval(if_)

        # 如果不符合, 则抛出异常
        if not if_:
            if msg_args:  # 如果存在额外的参数, 则拼接.否则返回空元组
                msg_args = (
                    # 如果是可迭代对象,并且不是字符串。就用join来拼接
                    ", ".join(item)
                    if isinstance(item, Iterable) and not isinstance(item, str)
                    else item
                    for item in msg_args
                )
                msg = msg.format(*msg_args)
            raise SystemExit(msg)  # 不需要回溯也能了解的
            # raise MyError(msg)  # 需要回溯的


    def decorate_lots(decorator):
        def run(cls):
            for k, func in cls.__dict__.items():
                if not re.match(r'__.+?__', k) and callable(func):
                    setattr(cls, k, decorator(func))
            return cls

        return run


    def print_mark(params=''):
        print(f'【标记】{str(params)}  {type(params)}')


    @contextlib.contextmanager
    def time_pass():
        s_time = time.time()
        yield
        duration = time.time()-s_time
        duration = round(duration, 2)  # 小数点保留两位
        print('运行时间：', duration)


    def drop_end(li: Iterable, func: callable = None):
        """连续删除元素, 从右边
        """
        # if not isinstance(li, list):
        #     li = list(li)
        # i = len(li)-1
        # while i >= 0 and func(li[i]):
        #     li.pop()
        #     i -= 1
        # return li
        li = list(li) if isinstance(li, Iterator) else li
        i = len(li)-1
        if isinstance(li, (tuple, str)):
            while i >= 0 and func(li[i]):
                i -= 1
            return li[:i+1]
        else:
            while i >= 0 and func(li[i]):
                li.pop()
                i -= 1
            return li

if "判断类型 isinstance":
    def is_int(obj):
        return isinstance(obj, int)


    def is_str(obj):
        return isinstance(obj, str)


    def is_list(obj):
        return isinstance(obj, list)


    def is_dict(obj):
        return isinstance(obj, dict)


    def is_not_none(obj):
        return obj is not None
if __name__ == '__main__':
    pass
    # _case_csv()
    # list(_case_csv())
